﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using LinFu.Reflection.Emit;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace CoreEx.Common.Extensions
{
    public static class TypeDefinitionExtensions
    {
        public static void AddStaticProperty(this TypeDefinition typeDefinition,string propertyName, TypeReference propertyType)
        {
            string fieldName = string.Format("__{0}_static_backingField", propertyName);
            var backingField = new FieldDefinition(fieldName, propertyType, FieldAttributes.Private | FieldAttributes.Static);
            typeDefinition.Fields.Add(backingField);

            var getterName = string.Format("get_{0}", propertyName);
            var setterName = string.Format("set_{0}", propertyName);

            var propertyAttributes =  MethodAttributes.Public | MethodAttributes.HideBySig |
                                                MethodAttributes.SpecialName | MethodAttributes.NewSlot |
                                                MethodAttributes.Static;

            TypeReference voidType = typeDefinition.Module.Import(typeof(void));
            
            // Implement the getter and the setter
            MethodDefinition getter = AddStaticPropertyGetter(propertyType, getterName, propertyAttributes, backingField);
            MethodDefinition setter = AddStaticPropertySetter(propertyType, propertyAttributes, backingField, setterName, voidType);

            typeDefinition.AddProperty(propertyName, propertyType, getter, setter);

        }

        private static MethodDefinition AddStaticPropertyGetter(TypeReference propertyType,
            string getterName, MethodAttributes attributes, FieldReference backingField)
        {
            var getter = new MethodDefinition(getterName, attributes, propertyType)
                             {
                                 IsPublic = true,
                                 IsStatic = true,
                ImplAttributes = (MethodImplAttributes.Managed | MethodImplAttributes.IL )
            };

            var IL = getter.GetILGenerator();            
            IL.Emit(OpCodes.Ldsfld, backingField);
            IL.Emit(OpCodes.Ret);

            return getter;
        }

        private static MethodDefinition AddStaticPropertySetter(TypeReference propertyType, MethodAttributes attributes, FieldReference backingField, string setterName, TypeReference voidType)
        {
            var setter = new MethodDefinition(setterName, attributes, voidType)
            {
                IsPublic = true,
                IsStatic = true,
                ImplAttributes = (MethodImplAttributes.Managed | MethodImplAttributes.IL)
            };

            setter.Parameters.Add(new ParameterDefinition(propertyType));

            var IL = setter.GetILGenerator();            
            IL.Emit(OpCodes.Ldarg_0);
            IL.Emit(OpCodes.Stsfld, backingField);
            IL.Emit(OpCodes.Ret);

            return setter;
        }
    }
}
